﻿using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
using System.Windows.Forms;

namespace LIK.NetworkTools
{
    internal static class NetworkScanner
    {
        private static readonly List<Ping> Pingers = new List<Ping>();
        private static int _instances = 0;
        private const int PING_TIMEOUT = 1000;
        private const int Ttl = 64;
        private static readonly Dictionary<IPAddress, string> ActiveIpAddressesm = new Dictionary<IPAddress, string>();
        private static readonly PingOptions PingOptions;
        private static readonly byte[] Buffer;
        private static readonly object SyncLock = new object();

        static NetworkScanner()
        {
            PingOptions = new PingOptions(Ttl, true);
            //System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
            //byte[] buffer = enc.GetBytes("abababababababababababababababab");
            Buffer = Encoding.ASCII.GetBytes(".");
        }

        public static Dictionary<IPAddress, string> ScanSingle(string clientIp)
        {
            lock (ActiveIpAddressesm)
            {
                ActiveIpAddressesm.Clear();
            }
            if (_instances != 0)
            {
                return ActiveIpAddressesm;
            }
            CreatePingers(1);

            //SpinWait wait = new SpinWait();
            int cnt = 1;

            foreach (Ping ping in Pingers)
            {
                lock (SyncLock)
                {
                    _instances += 1;
                }
                ping.SendAsync(clientIp, PING_TIMEOUT, Buffer, PingOptions);
                cnt += 1;
            }

            Stopwatch watch = new Stopwatch();
            watch.Start();
            while (WaitUntilAllPingsCompleted())
            {
                if (watch.Elapsed.TotalMinutes > 3)
                {
                    break;
                }
            }
            DestroyPingers();
            //Console.WriteLine("Finished in {0}. Found {1} active IP-addresses.", watch.Elapsed.ToString(), nIpAddresses);
            //Console.ReadKey();
            return ActiveIpAddressesm;
        }

        public static Dictionary<IPAddress, string> ScanNetwork(string baseIp, int excludedIp)
        {
            lock (ActiveIpAddressesm)
            {
                ActiveIpAddressesm.Clear();
            }
            if (_instances != 0)
            {
                return ActiveIpAddressesm;
            }
            //Console.WriteLine("Pinging 255 destinations of D-class in {0}*", baseIP);
            CreatePingers(255);

            //SpinWait wait = new SpinWait();
            int cnt = 1;

            foreach (Ping ping in Pingers)
            {
                if (cnt != excludedIp)
                {
                    lock (SyncLock)
                    {
                        _instances += 1;
                    }
                    ping.SendAsync(string.Concat(baseIp, cnt.ToString()), PING_TIMEOUT, Buffer, PingOptions);
                }
                cnt += 1;
            }
            Stopwatch watch = new Stopwatch();
            watch.Start();
            while (WaitUntilAllPingsCompleted())
            {
                if (watch.Elapsed.TotalMinutes > 3)
                {
                    break;
                }
            }
            DestroyPingers();
            //Console.WriteLine("Finished in {0}. Found {1} active IP-addresses.", watch.Elapsed.ToString(), nIpAddresses);
            //Console.ReadKey();
            return ActiveIpAddressesm;
        }

        private static bool WaitUntilAllPingsCompleted()
        {
            if (_instances==0) return false; // I'm assuming we can call this safely
            lock (SyncLock)
            {
                if (_instances == 0) return false;
                //Thread.SpinWait(500);
                //if (!wait.NextSpinWillYield)
                //{
                //    wait.SpinOnce();
                //} 
                //wait.SpinOnce();
                //Monitor.Wait(SyncLock, 500);
                Application.DoEvents();
                //Thread.Sleep(250);
            }
            return true;
        }

        private static void Ping_completed(object s, PingCompletedEventArgs e)
        {
            lock (SyncLock)
            {
                _instances -= 1;
            }
            if (e.Cancelled)
            {
                return;
            }
            if (e.Error != null)
            {
                return;
            }
            if (e.Reply.Status == IPStatus.Success || e.Reply.Status == IPStatus.Unknown)
            {
                lock (ActiveIpAddressesm)
                {
                    if (ActiveIpAddressesm.ContainsKey(e.Reply.Address))
                    {
                        ActiveIpAddressesm[e.Reply.Address] = e.Reply.Status.ToString();
                    }
                    else
                    {
                        ActiveIpAddressesm.Add(e.Reply.Address, e.Reply.Status.ToString());
                    }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cnt">size array</param>
        private static void CreatePingers(int cnt)
        {
            Pingers.Clear();
            for (int i = 1; i <= cnt; i++)
            {
                Ping p = new Ping();
                p.PingCompleted += Ping_completed;
                Pingers.Add(p);
            }
        }

        private static void DestroyPingers()
        {
            foreach (Ping p in Pingers)
            {
                p.PingCompleted -= Ping_completed;
                p.Dispose();
            }
            Pingers.Clear();
        }

        internal static bool IsOnline(string strClientIpAddress)
        {
            using (Ping pingSender = new Ping())
            {
                PingReply reply = pingSender.Send(strClientIpAddress, PING_TIMEOUT);
                return reply != null && reply.Status == IPStatus.Success;
            }
        }
    }
}